package group.rober.saber.lang;

import group.rober.saber.kit.BeanKit;
import group.rober.saber.kit.JSONKit;
import org.apache.commons.jxpath.AbstractFactory;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathNotFoundException;
import org.apache.commons.jxpath.Pointer;
import org.apache.commons.lang3.Validate;
import org.springframework.util.LinkedCaseInsensitiveMap;

import java.util.Map;

/**
 * Created by tisir<yangsong158@qq.com> on 2017-05-19
 */
public class DataBox extends LinkedCaseInsensitiveMap<Object> {

    private static final long serialVersionUID = -8545051570671246562L;
    
    JXPathContext context = null;

    protected void toggleInit(){
        if(context==null){
            context = JXPathContext.newContext(this);
            //xpath路径中,自动创建对象使用的factory
            context.setFactory(getCreateObjectFactory());
        }
    }

    /**
     * 把JavaBean转为DataBox
     * @param bean
     */
    public DataBox putFromBean(Object bean) {
        Map<String, Object> data =BeanKit.bean2Map(bean);
        this.putAll(data);
        return this;
    }

    /**
     * 将DataBox转换为JavaBean
     * @param classType
     * @param <T>
     * @return
     */
    public <T> T toBean(Class<T> classType){
        return BeanKit.map2Bean(this,classType);
    }

    public Map<String,Object> toMap(){
        return (Map<String,Object>)this;
    }

    protected AbstractFactory getCreateObjectFactory(){
        return new AbstractFactory(){
            public boolean createObject(JXPathContext context, Pointer pointer,
                                        Object parent, String name, int index){
                if (parent instanceof DataBox){
                    DataBox dtx = (DataBox)parent;
                    dtx.put(name,new DataBox());
                    return true;
                }else{
                    return false;
                }
            }
        };
    }

    protected String xpath(String p){
        if(p==null)return null;
        return p.replaceAll("\\.","/");
    }

    @SuppressWarnings("unchecked")
    public <T> T xGetObject(String expression,boolean strict,Class<T> classType){
        Object object = xGetObject(expression,strict);
        if(object==null)return null;
        if(classType.isAssignableFrom(object.getClass())){
            T ret = (T)object;
            //如果获取一个结果是Map类型,但是目标类型是DataBox,则封装转换一下
            if(classType.isAssignableFrom(DataBox.class)&&!(ret instanceof DataBox)&&ret instanceof Map){
                Map<String,Object> mapObject = (Map<String, Object>)ret;
                DataBox dataBox = new DataBox();
                dataBox.putAll(mapObject);
                ret = (T)dataBox;
            }

            return ret;
        }else{
            throw new ClassCastException(object.getClass()+" can not cast to "+classType);
        }
    }

    public <T> T xGetObject(String expression,Class<T> classType){
        return this.xGetObject(expression,false,classType);
    }
    /**
     * 取一个子对象
     * @param expression
     * @param strict
     * @return
     */
    public Object xGetObject(String expression, boolean strict){
        toggleInit();
        Object value = null;
        try{
            value = context.getValue(xpath(expression));
        }catch(JXPathNotFoundException e){  //如果表达式不存在,在严格模式下,也抛出错
            if(strict){
                throw new NullPointerException(e.getMessage());
            }
        }
        if(strict) Validate.notNull(value,"expression:"+expression,",not exits in object:"+toJsonString());
        return value;
    }

    /**
     * 获取一个值对象
     * @param expression xpath表达式
     * @param strict 是否使用严格模式
     * @return
     */
    public ValueObject xGet(String expression, boolean strict){
        Object value = xGetObject(expression,strict);
        return new ValueObject(value);
    }

    public DataBox xGetSubDataBox(String expression, boolean strict){
        Object value = xGetObject(expression,strict);
        if(value instanceof DataBox){
            return ((DataBox)value);
        }else{
            return null;
        }
    }

    public DataBox xGetSubDataBox(String expression){
        return xGetSubDataBox(expression,false);
    }

    public ValueObject xGet(String expression){
        return xGet(expression,false);
    }

    public void xPut(String expression, Object value){
        toggleInit();
        context.createPathAndSetValue(xpath(expression),value);
    }

    public String toJsonString(){
        return JSONKit.toJsonString(this);
    }

}
